#include "FxLoader.h"
//#include "ApplicationException.h"
//#include "InteropService.h"
#include <windows.h>

#include <comdef.h>
#include <metahost.h>
#include <mscoree.h>
#pragma comment(lib, "mscoree.lib") 

#include <stdexcept>
#include <string>
#include <sstream>
#include <direct.h>
#include <iostream>
using namespace std;

#define CHECK_ERR(hr) if(FAILED(hr)) throw runtime_error(w2a(_com_error(hr).ErrorMessage()));
static string w2a(const wstring& text);


unique_ptr<FxLoader> FxLoader::s_instance;
FxLoader::FxLoader(void)
	: m_clr(nullptr)
{
	CoInitializeEx(NULL, COINIT_MULTITHREADED);
	startup();
}
FxLoader::~FxLoader(void)
{
	try
	{
		shutdown();
	}
	catch(const exception& e)
	{
		cout << e.what() << endl;
	}
	CoUninitialize();
}
FxLoader& FxLoader::instance()
{
	// TODO: lock?
	if(!s_instance)
	{
		s_instance.reset(new FxLoader());
	}
	return *s_instance;
}
void FxLoader::shutdown()
{
	if(m_clr)
	{
		ICLRRuntimeHost *pClrHost = static_cast<ICLRRuntimeHost*>(m_clr);

		HRESULT hr = pClrHost->Stop();
		CHECK_ERR(hr);

		pClrHost->Release();
		m_clr = NULL;        
	}
}
void FxLoader::startup()
{
	ICLRRuntimeHost *pClrHost = nullptr;
	ICLRRuntimeInfo *lpRuntimeInfo = nullptr;
	ICLRMetaHost *lpMetaHost = nullptr;

	try
	{
		HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&lpMetaHost));
		CHECK_ERR(hr);

		hr = lpMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&lpRuntimeInfo));
		CHECK_ERR(hr);

		hr = lpRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID *)&pClrHost);
		CHECK_ERR(hr);

		hr = pClrHost->Start();
		CHECK_ERR(hr);
	}
	catch(const exception& e)
	{
		if(pClrHost) pClrHost->Release();
		if(lpRuntimeInfo) lpRuntimeInfo->Release();
		if(lpMetaHost) lpMetaHost->Release();
		throw e;
	}

	lpRuntimeInfo->Release();
	lpMetaHost->Release();

	// save
	m_clr = pClrHost;
}
void FxLoader::call(
		const wstring& pwzAssemblyPath, 
		const wstring& pwzTypeName, 
		const wstring& pwzMethodName,
        const wstring& pwzArgument)
{
	if(!m_clr)
	{
		throw runtime_error("CLR has not been started");
	}

	wstring cc = L"G:\\git\\noria\\miles\\mackinac\\Debug\\";
	cc += pwzAssemblyPath;

	ICLRRuntimeHost *pClrHost = static_cast<ICLRRuntimeHost*>(m_clr);
	DWORD dwRet;

	HRESULT hr = pClrHost->ExecuteInDefaultAppDomain(
		cc.c_str(),
		pwzTypeName.c_str(), 
		pwzMethodName.c_str(), 
		pwzArgument.c_str(), 
		&dwRet);
	CHECK_ERR(hr);

	if(dwRet != 0)
	{
		throw runtime_error("FxInvoke failed.");
	}
}


string w2a(const wstring& text)
{
	int nbytes = WideCharToMultiByte( 
		CP_UTF8, // specify the code page used to perform the conversion
		0,         // no special flags to handle unmapped characters
		text.c_str(),     // wide character string to convert
		text.length(),   // the number of wide characters in that string
		NULL,      // no output buffer given, we just want to know how long it needs to be
		0,
		NULL,      // no replacement character given
		NULL );    // we don't want to know if a character didn't make it through the translation

	// make sure the buffer is big enough for this, making it larger if necessary
	if(!nbytes)
		throw runtime_error("w2a failed; no sufficient memory");

	char* buff = new char[nbytes];

	WideCharToMultiByte( 
		CP_UTF8, // specify the code page used to perform the conversion
		0,         // no special flags to handle unmapped characters
		text.c_str(),   // wide character string to convert
		text.length(),   // the number of wide characters in that string
		buff, // put the output ascii characters at the end of the buffer
		nbytes,                           // there is at least this much space there
		NULL,      // no replacement character given
		NULL );

	string ansi(buff, nbytes);
	delete[] buff;

	return ansi;
}
